#region Copyright (c), Some Rights Reserved
/*##########################################################################
 * 
 * NeuralNetwork.cs
 * -------------------------------------------------------------------------
 * By
 * Murat FIRAT, September 2007
 * 
 * -------------------------------------------------------------------------
 * Description:
 * Contains Neural Network Class(BP1layer,BP2layer or BP3layer) And
 * Callback To Handle Training Process
 * 
 * -------------------------------------------------------------------------
 ###########################################################################*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Kinect;

namespace BPSimplified.Lib
{
    public class NeuralNetwork : INotifyPropertyChanged
    {
        private BP1Layer<string> NeuralNet;
        private double maximumError = 0.0001;
        private int maximumIteration = 3000;
        Dictionary<string, double[]> TrainingSet;

        public int averageHeight = 30;
        public int averageWidth = 20;
        private int NumOfPatterns = 0;

        BackgroundWorker worker = new BackgroundWorker();

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private decimal error;
        public decimal Error
        {
            get { return error; }
            set
            {
                error = value;
                OnPropertyChanged("Error");
            }
        }


        private string status;
        public string Status
        {
            get { return status; }
            set
            {
                status = value;
                OnPropertyChanged("Status");
            }
        }


        private string matchedHigh;
        public string MatchedHigh
        {
            get { return matchedHigh; }
            set
            {
                matchedHigh = value;
                OnPropertyChanged("MatchedHigh");
            }
        }

        public NeuralNetwork()
        {


            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.WorkerReportsProgress = true;

            worker.RunWorkerAsync();
        }

        public void ReTrain()
        {
            if (worker.IsBusy == false)
            {
                worker.RunWorkerAsync();
            }
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Status = "started";

            if (TrainingSet == null)
            {
                //all pattern files from directory "patterns"
                string[] Patterns = Directory.GetFiles(Path.GetFullPath("../../PATTERNS"), "*.bmp");

                TrainingSet = new Dictionary<string, double[]>(Patterns.Length);

                NumOfPatterns = Patterns.Length;

                //calculate average size of the images
                //foreach (string s in Patterns)
                //{
                //    BitmapImage Temp = new BitmapImage(new Uri(s));
                //    averageHeight += (int)Temp.PixelHeight;
                //    averageWidth += (int)Temp.PixelWidth;
                //}

                //averageHeight = (int)((double)averageHeight / (double)NumOfPatterns);
                //averageWidth = (int)((double)averageWidth / (double)NumOfPatterns);

                //.. just set them static

                foreach (string s in Patterns)
                {
                    //download image
                    BitmapImage Temp = new BitmapImage(new Uri(s));

                    //make le matrix
                    var matrix = BitmapToMatrix(Temp);

                    //store them
                    TrainingSet.Add(Path.GetFileNameWithoutExtension(s), matrix);
                }
            }

            //train!
            Train();
        }

        private void Train()
        {
            NeuralNet = new BP1Layer<string>(averageHeight * averageWidth, NumOfPatterns);
            NeuralNet.InitializeNetwork(TrainingSet);

            double currentError = 0;
            int currentIteration = 0;

            Status = "training";

            do
            {
                currentError = 0;

                foreach (KeyValuePair<string, double[]> p in TrainingSet)
                {
                    NeuralNet.ForwardPropagate(p.Value, p.Key);
                    NeuralNet.BackPropagate();
                    currentError += NeuralNet.GetError();
                }

                currentIteration++;

                if (currentIteration % 5 == 0)
                {
                    Error = decimal.Round((decimal)currentError, 5);
                }

            } while (currentError > maximumError && currentIteration < maximumIteration);




            if (currentIteration >= maximumIteration)
            {
                Status = "fail";
            }
            else
            {
                Status = "done";
            }

        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

        public void Recognize(double[] Input)
        {
            string MatchedHigh2 = "?", MatchedLow2 = "?";
            double OutputValueHight2 = 0, OutputValueLow2 = 0;

            NeuralNet.Recognize(Input, ref MatchedHigh2, ref OutputValueHight2, ref MatchedLow2, ref OutputValueLow2);

            MatchedHigh = MatchedHigh2 + " - " + decimal.Round((decimal)OutputValueHight2, 3);
        }

        public void Recognize(double[] Input, ref string MatchedHigh, ref double OutputValueHight, ref string MatchedLow, ref double OutputValueLow)
        {
            NeuralNet.Recognize(Input, ref MatchedHigh, ref OutputValueHight, ref MatchedLow, ref OutputValueLow);
        }

        public void SaveNetwork(string path)
        {
            FileStream FS = new FileStream(path, FileMode.Create);
            BinaryFormatter BF = new BinaryFormatter();
            BF.Serialize(FS, NeuralNet);
            FS.Close();
        }

        public void LoadNetwork(string path)
        {
            FileStream FS = new FileStream(path, FileMode.Open);
            BinaryFormatter BF = new BinaryFormatter();
            NeuralNet = (BP1Layer<string>)BF.Deserialize(FS);
            FS.Close();
        }

        Color InvertColor(Color ColorToInvert)
        {
            return Color.FromArgb((byte)255, (byte)(255 - ColorToInvert.R), (byte)(255 - ColorToInvert.G), (byte)(255 - ColorToInvert.B));
        }

        public double[] ByteArrayToMatrix(byte[] array)
        {
            double[] Result = new double[averageWidth * averageHeight];

            for (int r = 0; r < averageHeight; r++)
            {
                for (int c = 0; c < averageWidth; c++)
                {
                    Result[r * averageWidth + c] = array[r * averageWidth + c] / 255;
                }
            }

            return Result;
        }

        private double[] BitmapToMatrix(BitmapSource Temp)
        {
            Temp = FastResize(Temp, averageWidth, averageHeight);

            WriteableBitmap bibi = new WriteableBitmap(Temp);

            int stride = Temp.PixelWidth * ((Temp.Format.BitsPerPixel + 7) / 8);

            var pixelBytes = new byte[Temp.PixelHeight * stride];
            bibi.CopyPixels(pixelBytes, stride, 0);

            PixelColor[,] colors = new PixelColor[Temp.PixelWidth, Temp.PixelHeight];

            double[] Result = new double[averageWidth * averageHeight];

            for (int y = 0; y < Temp.PixelHeight; y++)
            {
                for (int x = 0; x < Temp.PixelWidth; x++)
                {
                    colors[x, y] = new PixelColor
                    {
                        Blue = (byte)(255 - pixelBytes[(y * Temp.PixelWidth + x) * 4 + 0]),
                        Green = (byte)(255 - pixelBytes[(y * Temp.PixelWidth + x) * 4 + 1]),
                        Red = (byte)(255 - pixelBytes[(y * Temp.PixelWidth + x) * 4 + 2]),
                        Alpha = pixelBytes[(y * Temp.PixelWidth + x) * 4 + 3],
                    };

                    Result[y * averageWidth + x] = pixelBytes[(y * Temp.PixelWidth + x) * 4] > 50 ? 1 : 0;
                }
            }



            // double HRate = ((Double)averageHeight / Temp.Height);
            //  double WRate = ((Double)averageWidth / Temp.Width);


            //for (int r = 0; r < averageHeight; r++)
            //{
            //    for (int c = 0; c < averageWidth; c++)
            //    {
            //       // var color = colors[(int)(c / WRate), (int)(r / HRate)];
            //        var color = colors[c, r];
            //        Result[r * averageWidth + c] = 1 - (color.Red * .3 + color.Green * .59 + color.Blue * .11) / 255;
            //    }
            //}

            return Result;
        }

        public BitmapSource FastResize(BitmapSource bfPhoto, int nWidth, int nHeight)
        {
            TransformedBitmap tbBitmap = new TransformedBitmap(bfPhoto, new ScaleTransform(nWidth / bfPhoto.Width, nHeight / bfPhoto.Height, 0, 0));
            return BitmapFrame.Create(tbBitmap);
        }

    }
}
